In [1]:
import sys, os
parentPath = os.path.abspath('..')
if not parentPath in sys.path:
    sys.path.append(parentPath)
from presentation import hide_code_button
hide_code_button()
In [2]:
import pandas as pd
import numpy as np

import analysis
from presentation import markprint
from functools import partial
average = analysis.average

%matplotlib inline
In [3]:
frm = analysis.make_melvic_dataFrm()
cases = analysis.make_casesFrm()
In [4]:
avStay = average(frm, 'adjstay')
rollingAvStay = avStay.rolling(7).mean().dropna()

stayScoreAv = average(frm, 'stayscore')

rollingScores = frm.reset_index().set_index(['date', 'name'])['stayscore'].rolling(7).mean()
maxDate = rollingScores.index.get_level_values('date').max()
lgaScores = pd.DataFrame(rollingScores.loc[maxDate]).reset_index()
lgaScores = lgaScores.rename(dict(stayscore = 'Score', name = 'LGA'), axis = 1)
lgaScores = lgaScores.set_index('LGA')
bestFive, worstFive = lgaScores.nlargest(5, 'Score'), lgaScores.nsmallest(5, 'Score')

Melbourne mobility report

These plots, based on Facebook location tracking data, show the changes in patterns of movement of tense of thousands of Facebook users in response to the COVID-19 pandemic. The data has been aggregated to Local Government Areas, typically city councils.

The stay-put quantity reflects the number of records provided by Facebook which show people moving beyond their immediate vicinity (about one kilometre in any direction), adjusted slightly for known sampling biases. The population-weighted average stay-put quantity has been plotted along with a seven-day rolling average, which smooths out daily variations. The horizontal axes are given in weeks starting Sundays.

Because of privacy concerns, Facebook's data is aggressively pruned wherever population counts are low, which leads to serious underestimates of movement for less densely populated areas. To correct this we have created a Lockdown Score, which normalises every value based off the averages of the best and worst values observed for that day of the week for that council. A score of one or higher indicates that people are equalling or exceeding their best ever stay-at-home behaviours. A score of zero or lower indicates that people are matching or trailing their worst ever behaviours. A weighted average lockdown score has been plotted, as well as the complete data for all councils below it.

For comparison, the new daily cases have been plotted at the bottom, while letter annotations correspond to the major changes in public perceptions and policies, as well as public holidays.

Full data, additional aggregations, and interactive plots are available on the website. The data are updated daily and the portal is continually being improved. If you have questions or suggestions, please contact Rohan Byrne.

This work is a collaboration with Professor Sally Cripps, Dr Roman Marchant, and Dr Vincent Chin of the DARE Centre and Rohan Byrne of the University of Melbourne with funding from the Australian Research Council (FT140101266).

In [5]:
from window.plot import Canvas, Data, get_cmap

dates = frm.index.get_level_values('date')
events_annotate = partial(
    analysis.events_annotate,
    region = 'vic',
    lims = (dates.min(), dates.max()),
    points = (0, 12)
    )

markprint('## Summary')

markprint('---')

ax = Canvas(size = (12, 3)).make_ax()
ax.set_title('Stay-put ratio: Melbourne average with 7-day rolling average')
ax.line(
    avStay.index,
    Data(avStay.values),
    )
ax.line(
    Data(rollingAvStay.index, label = 'Date'),
    Data(rollingAvStay.values, label = 'Proportion staying put'),
    )
table = events_annotate(ax, avStay)
markprint(table)
markprint('---')
display(ax.canvas.fig)

markprint('---')

ax = Canvas(size = (12, 3)).make_ax()
ax.set_title('Lockdown score: Melbourne average')
ax.line(
    Data(stayScoreAv.index, label = 'Date'),
    Data(stayScoreAv.values, label = 'Average stay-put score'),
    )
keystr = events_annotate(ax, stayScoreAv)
display(ax.canvas.fig)

markprint('---')

ax = Canvas(size = (12, 3)).make_ax()
ax.set_title('Lockdown score: Melbourne LGAs')
xs, ys, cs, ss, ls = [], [], [], [], []
cmap = get_cmap('gist_ncar')
for i, name in enumerate(sorted(set(frm['name']))):
    subFrm = frm.loc[frm['name'] == name]
    series = subFrm.reset_index().set_index('date')['stayscore']
    series = series.dropna()
    xs.append(Data(series.index, label = 'Date'))
    ys.append(Data(series.values, label = 'Score', lims = (-1., 2.)))
    cs.append(cmap(i / len(set(frm['name'])), alpha = 0.5))
    ss.append(None)
    ls.append(name)
ax.multiline(xs, ys, cs, ss, ls)
axPoints = frm.reset_index().groupby('date')['stayscore'].apply(max)
keystr = events_annotate(ax, axPoints)
ax.ax.legend(
    loc = 'lower left',
#     bbox_to_anchor = (0.5, -0.5),
#     ncol = 5,
#     bbox_to_anchor = (0.24, 0.52),
    ncol = 6,
    prop = dict(size = 6),
    fancybox = True,
#     shadow = True,
    framealpha = 0.5,
    )
display(ax.canvas.fig)

markprint('---')

ax = Canvas(size = (12, 3)).make_ax()
ax.set_title('Victorian daily active cases since the second wave')
sumCases = cases.groupby(cases.index.get_level_values('date'))['active'].sum()
ax.line(
    Data(sumCases.index, label = 'Date', lims = ('2020-04-12', stayScoreAv.index.max())),
    Data(sumCases.values, label = 'Active cases'),
    )
keystr = events_annotate(ax, sumCases)
display(ax.canvas.fig)

markprint('---')

Summary


Key Event
a ANZAC Day
b Testing blitz announced
c First lockdown ends
d Easing plan revealed
e 'Stay-safe' message softening
f 'Reasons to leave' message softening
g Cafes reopen
h Black Lives Matter protests
i Queen's Birthday
j Further easing revealed
k Easing halted
l Hot-spot testing blitz
m School holidays begin
n Hot-spot lockdown
o Tower lockdown
p City lockdown announced
q City lockdown in effect
r Face covering recommended
s Senior schools return
t Police crackdown announced
u Regional escalation
v Face covering announced
w All schools return





In [6]:
markprint("### Average scores for the past week per LGA:")
display(lgaScores)
markprint("#### Best five scores for past week:")
display(bestFive)
markprint("#### Worst five scores for past week:")
display(worstFive)

Average scores for the past week per LGA:

Score
LGA
Banyule 0.692596
Bayside 0.726394
Boroondara 0.733631
Brimbank 0.743145
Cardinia 0.685684
Casey 0.681513
Darebin 0.686642
Frankston 0.656732
Glen Eira 0.646180
Greater Dandenong 0.612243
Hobsons Bay 0.635636
Hume 0.656544
Kingston 0.676312
Knox 0.664990
Manningham 0.696324
Maribyrnong 0.706915
Maroondah 0.735223
Melbourne 0.731005
Melton 0.734500
Monash 0.723831
Moonee Valley 0.741693
Moreland 0.747354
Mornington Peninsula 0.688373
Nillumbik 0.728272
Port Phillip 0.718579
Stonnington 0.734481
Whitehorse 0.762829
Wyndham 0.758356
Yarra 0.757010

Best five scores for past week:

Score
LGA
Whitehorse 0.762829
Wyndham 0.758356
Yarra 0.757010
Moreland 0.747354
Brimbank 0.743145

Worst five scores for past week:

Score
LGA
Greater Dandenong 0.612243
Hobsons Bay 0.635636
Glen Eira 0.646180
Hume 0.656544
Frankston 0.656732
In [7]:
markprint('## Breakdown by LGA')

for name in sorted(set(frm['name'])):

    markprint(f'### {name}')

    subFrm = frm.loc[frm['name'] == name]
    subFrm = subFrm.reset_index().set_index('date')
    subStay = subFrm['adjstay'].dropna()
    rollingSubStay = subStay.rolling(7).mean().dropna()
    subScore = subFrm['stayscore'].dropna()

    dates = frm.index.get_level_values('date')
    minDate, maxDate = dates.min(), dates.max()

    ax = Canvas(size = (12, 3)).make_ax()
    ax.set_title('Stay-put ratio: average with 7-day rolling average')
    ax.line(
        Data(subStay.index, lims = (minDate, maxDate)),
        Data(subStay.values, lims = (None, 1.)),
        )
    ax.line(
        Data(rollingSubStay.index, label = 'Date', lims = (minDate, maxDate)),
        Data(rollingSubStay.values, label = 'Proportion staying put'),
        )
    keystr = events_annotate(ax, subStay)
    display(ax.canvas.fig)

    ax = Canvas(size = (12, 3)).make_ax()
    ax.set_title('Lockdown score')
    ax.line(
        Data(subScore.index, label = 'Date', lims = (minDate, maxDate)),
        Data(subScore.values, label = 'Stay-put score'),
        )
    keystr = events_annotate(ax, subScore)
    display(ax.canvas.fig)

    ax = Canvas(size = (12, 3)).make_ax()
    ax.set_title('Daily active cases since the second wave')
    subCases = cases.xs(name, level = 'name')['active'].dropna()
    ax.line(
        Data(subCases.index, label = 'Date', lims = ('2020-04-12', maxDate)),
        Data(subCases.values, label = 'Active cases'),
        )
    display(ax.canvas.fig)

    markprint('---')

Breakdown by LGA

Banyule


Bayside


Boroondara


Brimbank


Cardinia


Casey


Darebin


Frankston


Glen Eira


Greater Dandenong


Hobsons Bay


Hume


Kingston


Knox


Manningham


Maribyrnong


Maroondah


Melbourne


Melton


Monash


Moonee Valley


Moreland


Mornington Peninsula


Nillumbik

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/usr/local/lib/python3.6/dist-packages/IPython/core/formatters.py in __call__(self, obj)
    339                 pass
    340             else:
--> 341                 return printer(obj)
    342             # Finally look for special method names
    343             method = get_real_method(obj, self.print_method)

/usr/local/lib/python3.6/dist-packages/IPython/core/pylabtools.py in <lambda>(fig)
    246 
    247     if 'png' in formats:
--> 248         png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png', **kwargs))
    249     if 'retina' in formats or 'png2x' in formats:
    250         png_formatter.for_type(Figure, lambda fig: retina_figure(fig, **kwargs))

/usr/local/lib/python3.6/dist-packages/IPython/core/pylabtools.py in print_figure(fig, fmt, bbox_inches, **kwargs)
    130         FigureCanvasBase(fig)
    131 
--> 132     fig.canvas.print_figure(bytes_io, **kw)
    133     data = bytes_io.getvalue()
    134     if fmt == 'svg':

/usr/local/lib/python3.6/dist-packages/matplotlib/backend_bases.py in print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, **kwargs)
   2077                             print_method, dpi=dpi, orientation=orientation),
   2078                         draw_disabled=True)
-> 2079                     self.figure.draw(renderer)
   2080                     bbox_artists = kwargs.pop("bbox_extra_artists", None)
   2081                     bbox_inches = self.figure.get_tightbbox(renderer,

/usr/local/lib/python3.6/dist-packages/matplotlib/artist.py in draw_wrapper(artist, renderer, *args, **kwargs)
     36                 renderer.start_filter()
     37 
---> 38             return draw(artist, renderer, *args, **kwargs)
     39         finally:
     40             if artist.get_agg_filter() is not None:

/usr/local/lib/python3.6/dist-packages/matplotlib/figure.py in draw(self, renderer)
   1734             self.patch.draw(renderer)
   1735             mimage._draw_list_compositing_images(
-> 1736                 renderer, self, artists, self.suppressComposite)
   1737 
   1738             renderer.close_group('figure')

/usr/local/lib/python3.6/dist-packages/matplotlib/image.py in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    135     if not_composite or not has_images:
    136         for a in artists:
--> 137             a.draw(renderer)
    138     else:
    139         # Composite any adjacent images together

/usr/local/lib/python3.6/dist-packages/matplotlib/artist.py in draw_wrapper(artist, renderer, *args, **kwargs)
     36                 renderer.start_filter()
     37 
---> 38             return draw(artist, renderer, *args, **kwargs)
     39         finally:
     40             if artist.get_agg_filter() is not None:

/usr/local/lib/python3.6/dist-packages/matplotlib/axes/_base.py in draw(self, renderer, inframe)
   2628             renderer.stop_rasterizing()
   2629 
-> 2630         mimage._draw_list_compositing_images(renderer, self, artists)
   2631 
   2632         renderer.close_group('axes')

/usr/local/lib/python3.6/dist-packages/matplotlib/image.py in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    135     if not_composite or not has_images:
    136         for a in artists:
--> 137             a.draw(renderer)
    138     else:
    139         # Composite any adjacent images together

/usr/local/lib/python3.6/dist-packages/matplotlib/artist.py in draw_wrapper(artist, renderer, *args, **kwargs)
     36                 renderer.start_filter()
     37 
---> 38             return draw(artist, renderer, *args, **kwargs)
     39         finally:
     40             if artist.get_agg_filter() is not None:

/usr/local/lib/python3.6/dist-packages/matplotlib/text.py in draw(self, renderer)
   2355             return
   2356 
-> 2357         xy_pixel = self._get_position_xy(renderer)
   2358         if not self._check_xy(renderer, xy_pixel):
   2359             return

/usr/local/lib/python3.6/dist-packages/matplotlib/text.py in _get_position_xy(self, renderer)
   1903         "Return the pixel position of the annotated point."
   1904         x, y = self.xy
-> 1905         return self._get_xy(renderer, x, y, self.xycoords)
   1906 
   1907     def _check_xy(self, renderer, xy_pixel):

/usr/local/lib/python3.6/dist-packages/matplotlib/text.py in _get_xy(self, renderer, x, y, s)
   1769             x = float(self.convert_xunits(x))
   1770         if s2 == 'data':
-> 1771             y = float(self.convert_yunits(y))
   1772         return self._get_xy_transform(renderer, s).transform((x, y))
   1773 

/usr/local/lib/python3.6/dist-packages/pandas/core/series.py in wrapper(self)
    110         if len(self) == 1:
    111             return converter(self.iloc[0])
--> 112         raise TypeError(f"cannot convert the series to {converter}")
    113 
    114     wrapper.__name__ = f"__{converter.__name__}__"

TypeError: cannot convert the series to <class 'float'>
<Figure size 1200x300 with 1 Axes>

Port Phillip


Stonnington


Whitehorse


Wyndham


Yarra


In [ ]: